package services

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"github.com/shopspring/decimal"
	"io/ioutil"
	"ir/core"
	"ir/core/db"
	"ir/models/structs"
	"ir/models/tables"
	"log"
	"math"
	"net/http"
	"strings"
	"time"
)

const OrderDealPart = 1
const OrderDealAll = 10

func AddOrder(orderBook tables.OrderBook) (err error) {
	fmt.Println(orderBook)
	// 订单入库
	conn := db.GetRedis()
	var dAmount decimal.Decimal
	dAmount, err = decimal.NewFromString(orderBook.Amount)
	if err != nil {
		return
	}
	var dPrice decimal.Decimal
	dPrice, err = decimal.NewFromString(orderBook.Price)
	if err != nil {
		return
	}
	fAmount, _ := dAmount.Float64()
	fPrice, _ := dPrice.Float64()
	symbolOrder := structs.SymbolOrder{
		ID:      orderBook.Sn,
		UserId:  orderBook.UserId,
		Amount:  fAmount,
		Price:   fPrice,
		Side:    orderBook.Side,
		Created: orderBook.Created,
	}
	order := structs.Order{
		SymbolOrder: symbolOrder,
		Symbol:      orderBook.Symbol,
		Ip:          orderBook.Ip,
	}
	var reply interface{}
	sideName := "buy"
	if orderBook.Side == 2 {
		sideName = "sell"
	}
	reply, err = conn.Do("publish", sideName, order.ToJSON())
	log.Println("publish reply", sideName)
	log.Println(reply)
	if err != nil {
		return
	}
	return
}

func PickTradeOrderData(orderBook tables.OrderBook) (order structs.Order, err error) {
	var dAmount decimal.Decimal
	dAmount, err = decimal.NewFromString(orderBook.Amount)
	if err != nil {
		err = errors.New("数量错误")
		return
	}

	if dAmount.LessThanOrEqual(decimal.NewFromInt(0)) {
		err = errors.New("数量需大于0")
		return
	}

	var dPrice decimal.Decimal
	dPrice, err = decimal.NewFromString(orderBook.Price)

	if err != nil {
		err = errors.New("价格错误")
		return
	}

	if dPrice.LessThanOrEqual(decimal.NewFromInt(0)) {
		err = errors.New("价格需大于0")
		return
	}
	fAmount, _ := dAmount.Float64()
	fPrice, _ := dPrice.Float64()
	symbolOrder := structs.SymbolOrder{
		ID:      orderBook.Sn,
		UserId:  orderBook.UserId,
		Amount:  fAmount,
		Price:   fPrice,
		Side:    orderBook.Side,
		Created: orderBook.Created,
	}
	order = structs.Order{
		SymbolOrder: symbolOrder,
		Type:        "trade",
		Symbol:      orderBook.Symbol,
		Ip:          orderBook.Ip,
	}
	return
}

// AddBuyOrder 添加买订单 降序
func AddBuyOrder(key string, newOrder structs.SymbolOrder) (err error) {
	conn := db.GetRedis()
	var reply interface{}
	reply, err = conn.Do("llen", key)
	iLen := reply.(int64)
	if iLen == 0 {
		_, err = conn.Do("lpush", key, string(newOrder.ToJSON()))
		return
	}
	var size float64 = 100
	fPage := float64(iLen) / size
	iPage := int64(math.Ceil(fPage))
	var i int64 = 1
	var iSize = int64(size)
	for ; i <= iPage; i++ {
		iStart := (i - 1) * iSize
		iEnd := iStart + iSize
		var bArr [][]byte
		bArr, err = redis.ByteSlices(conn.Do("lrange", key, iStart, iEnd))
		if err != nil {
			break
		}
		n := len(bArr)
		for j := 0; j < n; j++ {
			bItem := bArr[j]
			order := structs.SymbolOrder{}
			err = order.FromJSON(bItem)
			if err != nil {
				return
			}
			if newOrder.Price > order.Price {
				rowStr := string(bItem)
				_, err = conn.Do("linsert", key, "BEFORE", rowStr, string(newOrder.ToJSON()))
				return
			}
		}
	}
	_, err = conn.Do("rpush", key, string(newOrder.ToJSON()))
	return
}

// AddSellOrder 添加卖订单 升序
func AddSellOrder(key string, newOrder structs.SymbolOrder) (err error) {
	conn := db.GetRedis()
	var reply interface{}
	reply, err = conn.Do("llen", key)
	iLen := reply.(int64)
	if iLen == 0 {
		_, err = conn.Do("lpush", key, string(newOrder.ToJSON()))
		return
	}
	var size float64 = 100
	fPage := float64(iLen) / size
	iPage := int64(math.Ceil(fPage))
	var i = iPage
	var iSize = int64(size)
	for ; i > 0; i-- {
		iStart := (i - 1) * iSize
		iEnd := iStart + iSize
		var bArr [][]byte
		bArr, err = redis.ByteSlices(conn.Do("lrange", key, iStart, iEnd))
		if err != nil {
			break
		}
		n := len(bArr)
		for ; n > 0; n-- {
			bItem := bArr[n-1]
			order := structs.SymbolOrder{}
			err = order.FromJSON(bItem)
			if err != nil {
				return
			}
			if newOrder.Price > order.Price {
				rowStr := string(bItem)
				_, err = conn.Do("linsert", key, "AFTER", rowStr, string(newOrder.ToJSON()))
				return
			}
		}
	}
	_, err = conn.Do("lpush", key, string(newOrder.ToJSON()))
	return
}

func FindMatchOrders(newOrder structs.Order) (matchOrders []structs.Order, err error) {
	// 从委托交易列表里匹配新订单，如有价格满足交易条件，添加到匹配列表，直到数量达到新订单数量。
	matchOrders = make([]structs.Order, 0)
	matchOrderTrait := structs.Order{Symbol: newOrder.Symbol}
	dTotal := decimal.NewFromFloat(newOrder.Amount)
	dAmount := decimal.NewFromInt(0)
	if newOrder.Side == 1 { // 买
		matchOrderTrait.SymbolOrder = structs.SymbolOrder{Side: 2}
		mKey := matchOrderTrait.Key()
		var reply interface{}
		conn := db.GetRedis()
		reply, err = conn.Do("llen", mKey)
		if reply == nil {
			return
		}
		iLen := reply.(int64) // 卖单总数
		if iLen == 0 {
			return
		}
		var size float64 = 100
		fPage := float64(iLen) / size
		iPage := int64(math.Ceil(fPage))
		var i int64 = 1
		var iSize = int64(size)
		for ; i <= iPage; i++ {
			iStart := (i - 1) * iSize
			iEnd := iStart + iSize
			var bArr [][]byte
			bArr, err = redis.ByteSlices(conn.Do("lrange", mKey, iStart, iEnd))
			if err != nil {
				break
			}
			n := len(bArr)
			for j := 0; j < n; j++ {
				bItem := bArr[j]
				order := structs.SymbolOrder{}
				err = order.FromJSON(bItem)
				if err != nil {
					return
				}
				if newOrder.Price < order.Price { // 买家出价低于卖家最低价，直接返回
					return
				} else {
					if dAmount.GreaterThanOrEqual(dTotal) {
						return
					}
					matchOrder := structs.Order{
						SymbolOrder: order,
						Index:       iStart + int64(j),
						Symbol:      newOrder.Symbol,
					}
					dMatchOrderAmount := decimal.NewFromFloat(order.Amount)
					if err != nil {
						// TODO 标记待匹配记录
						continue
					}
					matchOrders = append(matchOrders, matchOrder)
					dAmount = dAmount.Add(dMatchOrderAmount)
				}
			}
		}
	} else if newOrder.Side == 2 { // 卖
		matchOrderTrait.SymbolOrder = structs.SymbolOrder{Side: 1}
		mKey := matchOrderTrait.Key()
		conn := db.GetRedis()
		var reply interface{}
		reply, err = conn.Do("llen", mKey)

		if reply == nil {
			return
		}

		iLen := reply.(int64) // 买单总数
		if iLen == 0 {
			return
		}
		var size float64 = 100
		fPage := float64(iLen) / size
		iPage := int64(math.Ceil(fPage))
		var i int64 = 1
		var iSize = int64(size)
		for ; i <= iPage; i++ {
			iStart := (i - 1) * iSize
			iEnd := iStart + iSize
			var bArr [][]byte
			bArr, err = redis.ByteSlices(conn.Do("lrange", mKey, iStart, iEnd))
			if err != nil {
				break
			}
			n := len(bArr)
			for j := 0; j < n; j++ {
				bItem := bArr[j]
				order := structs.SymbolOrder{}
				err = order.FromJSON(bItem)
				if err != nil {
					return
				}
				if newOrder.Price > order.Price {
					return
				} else {
					if dAmount.GreaterThanOrEqual(dTotal) {
						return
					}
					matchOrder := structs.Order{
						SymbolOrder: order,
						Index:       iStart + int64(j),
						Symbol:      newOrder.Symbol,
					}
					dMatchOrderAmount := decimal.NewFromFloat(order.Amount)
					if err != nil {
						// TODO 标记待匹配记录
						continue
					}
					matchOrders = append(matchOrders, matchOrder)
					dAmount = dAmount.Add(dMatchOrderAmount)
				}
			}
		}
	}
	return
}

// PopDealOrder 弹出已处理订单
func PopDealOrder(order structs.Order) (err error) {
	if order.ID == "" || order.Index == -1 {
		err = errors.New("无效的订单")
		return
	}
	key := order.Key()
	rc := db.GetRedis()
	_, err = rc.Do("blpop", key, 3)
	return
}

func RemoveOrder(order structs.Order) (err error) {
	key := order.Key()
	value := order.SymbolOrder.ToJSON()
	rc := db.GetRedis()
	_, err = rc.Do("lrem", key, 1, string(value))
	fmt.Println("value", string(value))
	return
}

// UpdateQueueOrder 更新队列订单
func UpdateQueueOrder(order structs.Order) (err error) {
	if order.ID == "" {
		err = errors.New("无效的订单")
		return
	}
	key := order.Key()
	rc := db.GetRedis()
	rc.Do("multi")
	_, err = rc.Do("blpop", key, 3)
	if err != nil {
		return
	}
	_, err = rc.Do("lpush", key, order.SymbolOrder.ToJSON())
	if err != nil {
		rc.Do("discard")
		return
	}
	rc.Do("exec")
	return
}

func dealCallback(txOrder tables.TxOrder) (err error) {
	config := core.GetConfig()
	dealCallbackUrl := config.GetString("TradeSystemBaseUrl") + "deal"
	method := "POST"
	var bTxOrder []byte
	bTxOrder, err = json.Marshal(txOrder)
	payload := strings.NewReader(string(bTxOrder))
	client := &http.Client{Timeout: time.Second * 15}
	req, err := http.NewRequest(method, dealCallbackUrl, payload)
	if err != nil {
		fmt.Println(err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	res, err := client.Do(req)
	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		fmt.Println(err)
		return
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	var joData = make(map[string]interface{})
	err = json.Unmarshal(body, &joData)
	if err != nil {
		if string(body) == "\"invalid argument\"" {
			err = errors.New("参数无效。")
		} else {
			err = errors.New("服务端响应错误。")
		}
		return
	}
	if iCode, ok := joData["errcode"].(float64); ok {
		if iCode != 0 {
			err = errors.New("服务端处理失败。")
		} else {
			// TODO 回调成功，更新状态，之后续应定时处理回调失败
			txStatus := tables.TxOrder{
				Status: 1,
			}
			db.Conn().ID(txOrder.Id).Update(txStatus)
		}
	} else {
		err = errors.New("服务端响应错误。")
	}
	return
}

// DealMatchedOrder 处理匹配订单
func DealMatchedOrder(orderBefore structs.Order, matchedOrder structs.Order) (orderAfter structs.Order, err error) {
	orderAfter = orderBefore
	dOrderBeforeAmount := decimal.NewFromFloat(orderBefore.Amount)
	if err != nil {
		return
	}
	if matchedOrder.ID == "" || !(dOrderBeforeAmount.GreaterThan(decimal.NewFromInt(0))) { // 没有匹配到
		return
	}
	var buyOrder structs.Order
	var sellOrder structs.Order
	if orderBefore.Side == 1 {
		buyOrder = orderBefore
		sellOrder = matchedOrder
	} else if orderBefore.Side == 2 {
		buyOrder = matchedOrder
		sellOrder = orderBefore
	} else {
		err = errors.New("side value error")
		return
	}
	now := time.Now().Unix()
	var affected int64
	dNewOrderPrice := decimal.NewFromFloat(orderBefore.Price)
	dMatchPrice := decimal.NewFromFloat(matchedOrder.Price)
	var dDealPrice decimal.Decimal
	if dNewOrderPrice.GreaterThanOrEqual(dMatchPrice) {
		dDealPrice = dMatchPrice
	} else {
		dDealPrice = dNewOrderPrice
	}

	sess := db.Conn()
	orderBooks := make([]tables.OrderBook, 0)
	sess.Cols("id", "sn", "side", "status", "locked", "ver").Where("sn in (?, ?)", orderBefore.ID, matchedOrder.ID).Find(&orderBooks)

	orderNum := len(orderBooks)

	if orderNum != 2 {
		err = errors.New("成交数据错误1")
		return
	}
	orderBookBefore := tables.OrderBook{}
	orderBookMatched := tables.OrderBook{}
	flag := 0
	for _, ob := range orderBooks {
		fmt.Println(ob.Sn, "=", orderBefore.ID, matchedOrder.ID)
		if ob.Sn == orderBefore.ID {
			orderBookBefore = ob
			flag++
		} else if ob.Sn == matchedOrder.ID {
			orderBookMatched = ob
			flag++
		}
	}
	if flag != 2 {
		err = errors.New("成交数据错误2")
		return
	}

	if orderBookBefore.Locked != 0 || orderBookMatched.Locked != 0 {
		err = errors.New("成交订单一方已取消交易")
		return
	}

	if orderBefore.Amount == matchedOrder.Amount { // 抵消，将匹配订单从redis中删除，并且标记库中记录

		err = sess.Begin()
		if err != nil {
			return
		}
		defer sess.Close()

		affected, err = sess.Where("id=? and ver=?", orderBookBefore.Id, orderBookBefore.Ver).Update(tables.OrderBook{
			Updated: now,
			Amount:  "0",
			Status:  OrderDealAll,
			Ver:     orderBookBefore.Ver + 1,
		})

		if err != nil {
			return
		}
		if affected == 0 {
			sess.Rollback()
			err = errors.New("没有更新")
			return
		}

		affected, err = sess.Where("id=? and ver=?", orderBookMatched.Id, orderBookMatched.Ver).Update(tables.OrderBook{
			Updated: now,
			Amount:  "0",
			Status:  OrderDealAll,
			Ver:     orderBookMatched.Ver + 1,
		})

		if err != nil {
			return
		}
		if affected == 0 {
			sess.Rollback()
			err = errors.New("没有更新")
			return
		}

		dAmount := decimal.NewFromFloat(matchedOrder.Amount)
		txOrder := tables.TxOrder{
			Sn1:     buyOrder.ID,
			Sn2:     sellOrder.ID,
			Uid1:    buyOrder.UserId,
			Uid2:    sellOrder.UserId,
			Symbol:  orderBefore.Symbol,
			Price:   dDealPrice.String(),
			Amount:  dAmount.String(),
			Created: now,
		}
		affected, err = sess.Insert(&txOrder)
		if err != nil {
			sess.Rollback()
			return
		}
		if affected == 0 {
			err = errors.New("新增失败")
			sess.Rollback()
			return
		}
		err = PopDealOrder(matchedOrder)
		if err != nil {
			sess.Rollback()
			return
		}
		err = sess.Commit()

		if err != nil {
			log.Println("commit err 1", err)
			return
		}

		err = dealCallback(txOrder)
		if err != nil {
			log.Println("回调处理失败", err)
		}

		orderAfter.Amount = 0
	} else if orderBefore.Amount > matchedOrder.Amount { // 新订单超过库存，剩余订单side于新订单side相同，添加剩余订单到redis，匹配订单从redis删除
		dMatchOrderAmount := decimal.NewFromFloat(matchedOrder.Amount)
		dSurplusAmount := dOrderBeforeAmount.Sub(dMatchOrderAmount)
		err = sess.Begin()

		if err != nil {
			return
		}
		defer sess.Close()

		// 更新新订单状态
		affected, err = sess.Where("id=? and ver=?", orderBookBefore.Id, orderBookBefore.Ver).Update(tables.OrderBook{
			Updated: now,
			Amount:  dSurplusAmount.String(),
			Status:  OrderDealPart, // 部分成交
			Ver:     orderBookBefore.Ver + 1,
		})

		if err != nil {
			return
		}
		if affected == 0 {
			err = errors.New("没有更新新订单")
			return
		}

		// 更新匹配订单状态
		affected, err = sess.Where("id=? and ver=?", orderBookMatched.Id, orderBookMatched.Ver).Update(tables.OrderBook{
			Updated: now,
			Amount:  "0",
			Status:  OrderDealAll, // 完全成交
			Ver:     orderBookMatched.Ver + 1,
		})

		if err != nil {
			sess.Rollback()
			return
		}
		if affected == 0 {
			sess.Rollback()
			err = errors.New("没有更新匹配订单")
			return
		}
		txOrder := tables.TxOrder{
			Sn1:     buyOrder.ID,
			Sn2:     sellOrder.ID,
			Uid1:    buyOrder.UserId,
			Uid2:    sellOrder.UserId,
			Symbol:  orderBefore.Symbol,
			Price:   dDealPrice.String(),
			Amount:  dMatchOrderAmount.String(),
			Created: now,
		}
		affected, err = sess.Insert(&txOrder)
		if err != nil {
			sess.Rollback()
			return
		}
		if affected == 0 {
			err = errors.New("添加成交订单失败")
			sess.Rollback()
			return
		}

		err = PopDealOrder(matchedOrder) // 标记匹配单为已成交
		if err != nil {
			sess.Rollback()
			return
		}
		err = sess.Commit()

		if err != nil {
			log.Println("commit err 2", err)
			return
		}

		err = dealCallback(txOrder)
		if err != nil {
			log.Println("回调处理失败", err)
		}

		orderAfter.Amount, _ = dSurplusAmount.Float64()
	} else if orderBefore.Amount < matchedOrder.Amount { // 新订单小于库存，剩余订单side取反新订单side
		dMatchOrderAmount := decimal.NewFromFloat(matchedOrder.Amount)
		dSurplusAmount := dMatchOrderAmount.Sub(dOrderBeforeAmount)

		// 更新匹配订单数量
		matchedOrder.Amount, _ = dSurplusAmount.Float64()
		err = UpdateQueueOrder(matchedOrder)
		if err != nil {
			return
		}

		err = sess.Begin()
		if err != nil {
			return
		}
		defer sess.Close()

		// 更新新订单状态
		affected, err = sess.Where("id=? and ver=?", orderBookBefore.Id, orderBookBefore.Ver).Update(tables.OrderBook{
			Updated: now,
			Amount:  "0",
			Status:  OrderDealAll, // 完全成交
			Ver:     orderBookBefore.Ver + 1,
		})

		if err != nil {
			return
		}
		if affected == 0 {
			err = errors.New("没有更新新订单")
			return
		}

		// 更新匹配订单状态
		affected, err = sess.Where("id=? and ver=?", orderBookMatched.Id, orderBookMatched.Ver).Update(tables.OrderBook{
			Updated: now,
			Amount:  dSurplusAmount.String(),
			Status:  OrderDealPart, // 部分成交
			Ver:     orderBookMatched.Ver + 1,
		})

		if err != nil {
			sess.Rollback()
			return
		}
		if affected == 0 {
			sess.Rollback()
			err = errors.New("没有更新匹配订单")
			return
		}

		txOrder := tables.TxOrder{
			Sn1:     buyOrder.ID,
			Sn2:     sellOrder.ID,
			Uid1:    buyOrder.UserId,
			Uid2:    sellOrder.UserId,
			Symbol:  orderBefore.Symbol,
			Price:   dDealPrice.String(),
			Amount:  dOrderBeforeAmount.String(),
			Created: now,
		}
		affected, err = sess.Insert(&txOrder)
		if err != nil {
			sess.Rollback()
			return
		}
		if affected == 0 {
			err = errors.New("添加成交订单失败")
			sess.Rollback()
			return
		}

		err = sess.Commit()
		if err != nil {
			log.Println("commit err 3", err)
			return
		}

		err = dealCallback(txOrder)
		if err != nil {
			log.Println("回调处理失败", err)
		}

		orderAfter.Amount = 0
	}
	return
}

// 通知交易系统收到新委托订单
func notifyHasOrder(order structs.Order) (err error) {
	config := core.GetConfig()
	notifyUrl := config.GetString("TradeSystemBaseUrl") + "notify_has_order"
	method := "POST"
	client := &http.Client{Timeout: time.Second * 15}
	var bPostData []byte
	pd := struct {
		Symbol string `json:"symbol"`
	}{Symbol: order.Symbol}
	bPostData, err = json.Marshal(pd)
	payload := strings.NewReader(string(bPostData))
	req, err := http.NewRequest(method, notifyUrl, payload)
	if err != nil {
		fmt.Println(err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	res, err := client.Do(req)
	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		fmt.Println(err)
		return
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	var joData = make(map[string]interface{})
	err = json.Unmarshal(body, &joData)
	if err != nil {
		if string(body) == "\"invalid argument\"" {
			err = errors.New("参数无效。")
		} else {
			err = errors.New("服务端响应错误。")
		}
		return
	}
	return
}

// DealMatchedOrders TODO 高并发
func DealMatchedOrders(newOrder structs.Order, matchedOrders []structs.Order) (err error) {
	if !(matchedOrders != nil && len(matchedOrders) > 0) {
		return
	}

	for i := 0; i < len(matchedOrders); i++ {
		newOrder, err = DealMatchedOrder(newOrder, matchedOrders[i])
		log.Println("err", err)
	}

	err = PushToQueue(newOrder)

	if err != nil {
		log.Println("添加新订单到队列失败", err)
	}

	go func() {
		err = notifyHasOrder(newOrder)
		if err != nil {
			log.Println("通知新订单失败", err)
		}
	}()
	return
}

func NotifyOrder(sn, event string) {
	config := core.GetConfig()
	notifyUrl := config.GetString("TradeSystemBaseUrl") + "notify"
	method := "POST"
	client := &http.Client{Timeout: time.Second * 15}
	pd := struct {
		Sn    string `json:"sn"`
		Event string `json:"event"`
	}{Sn: sn, Event: event}
	bPostData, err := json.Marshal(pd)
	payload := strings.NewReader(string(bPostData))
	req, err := http.NewRequest(method, notifyUrl, payload)
	if err != nil {
		fmt.Println(err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	res, err := client.Do(req)
	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		fmt.Println(err)
		return
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	var joData = make(map[string]interface{})
	err = json.Unmarshal(body, &joData)
	if err != nil {
		if string(body) == "\"invalid argument\"" {
			err = errors.New("参数无效。")
		} else {
			err = errors.New("服务端响应错误。")
		}
		return
	}
	return
}
